package ci.web.router;

import io.netty.handler.codec.http.HttpResponseStatus;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;

import ci.web.HttpMethod;
import ci.web.annotaction.BodyParam;
import ci.web.annotaction.FileParam;
import ci.web.annotaction.LimitMethod;
import ci.web.annotaction.Param;
import ci.web.annotaction.PathParam;
import ci.web.annotaction.Router;
import ci.web.codec.FileItem;
import ci.web.core.CiContext;

import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.util.TypeUtils;

/**
 * 路由-脚本处理器
 * @author zhh
 */
public class CiHandler implements CiCall{

    protected String path;
    protected String className;
    protected String methodName;
    protected Class<?>[] paramTypes;
    protected boolean pathArg;
    protected int _hashcode;
    
    public CiHandler(String pkg, Method method) {
        className = method.getDeclaringClass().getCanonicalName();
        methodName = method.getName();
        paramTypes = method.getParameterTypes();
        path = buildPath(method, pkg, className, methodName);
        pathArg = isPathArg(method);
        _hashcode = pathArg?(path+"@").hashCode():path.hashCode();
    }
    protected CiHandler(){
    }
    
    @Override
    public int hashCode() {
        return _hashcode;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof CiHandler){
            return obj==this;
        }
        return super.equals(obj);
    }
    
    @Override
    public String toString() {
        return String.format("{p:%s, j:%s.%s, p:%d}", path, className, methodName, paramTypes.length);
    }

    /**
     * 执行处理
     * @param ctx
     * @return
     * @throws Exception
     */
    @Override
    public boolean call(CiContext ctx, ClassLoader loader) throws Exception {
        Class<?> clazz = loader.loadClass(className);
        Method method = clazz.getMethod(methodName, paramTypes);
        if(isAlowMethod(method, ctx.in().method())==false){
            ctx.out().setStatus(HttpResponseStatus.FORBIDDEN);
        }else{
            Object[] args = castArgs(ctx, method, path);
            if (Modifier.isStatic(method.getModifiers())) {
                method.invoke(null, args);
            } else {
                Object instance = clazz.newInstance();
                method.invoke(instance, args);
            }
        }
        return true;
    }
    /**
     * 方法是否呗允许
     * @param method
     * @param requestMethod
     * @return
     */
    protected static boolean isAlowMethod(Executable method, HttpMethod requestMethod) {
        LimitMethod t = method.getAnnotation(LimitMethod.class);
        if(t!=null){
            return t.value()==requestMethod;
        }
        return true;
    }
    /**
     * 参数转换
     * @param ctx
     * @param method
     * @param basePath
     * @return
     */
    protected static Object[] castArgs(CiContext ctx, Executable method, String basePath) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Object[] args = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> clazz = parameterTypes[i];
            Object value = null;
            //请求上下文做为参数
            if (clazz.isAssignableFrom(CiContext.class)) {
                value = ctx;
            } else {
                Annotation[] annotations = parameterAnnotations[i];
                if (annotations.length == 1) {
                    if (annotations[0] instanceof Param) {
                      //普通-请求参数
                        String name = ((Param) annotations[0]).value();
                        Object paramValue = ctx.in().params().get(name);
                        value = TypeUtils.cast(paramValue, clazz, ParserConfig.getGlobalInstance());
                    } else if (annotations[0] instanceof BodyParam) {
                        //使用body做为参数，直接获取其byte数组
                        value = ctx.in().body();
                    } else if(annotations[0] instanceof FileParam){
                        //获取文件做为参数
                        String name = ((FileParam) annotations[0]).value();
                        FileItem item = ctx.in().getFile(name);
                        value = item==null?null:item.getFile();
                    } else if(annotations[0] instanceof PathParam){
                        //路径参数处理- 路径删除Class.Method然后做为参数, 这里支持两种
                        // 1.路径做参数 2.使用斜杠切分指定某一位做参数
                        String s = ctx.path().replace(basePath, "").substring(1);
                        if(clazz == String[].class){
                            value = s.split("/");
                        }else{
                            int idx = ((PathParam) annotations[0]).value();
                            if(idx<0){
                                value = s;
                            }else{
                                String[] sarr = s.split("/");
                                value = idx>=sarr.length ? null:sarr[idx];
                            }
                            value = TypeUtils.cast(value, clazz, ParserConfig.getGlobalInstance());
                        }
                    }
                }
            }
            args[i] = value;
        }
        return args;
    }
    
    /**
     * 构建路由路径
     * @param method
     * @param pkg
     * @param className
     * @param methodName
     * @return
     */
    protected static String buildPath(Executable method, String pkg, String className, String methodName){
        Router r = method.getDeclaringClass().getAnnotation(Router.class);
        if(r != null){
            className = r.value().toLowerCase();
        }else{
            className = className.toLowerCase().replace(pkg, "").replaceAll("\\.", "/");
        }
//        r = method.getAnnotation(Router.class);
//        if(r != null){
//            methodName = r.value();
//        }
        methodName = methodName.toLowerCase();
        if((className.isEmpty() && methodName.isEmpty()) || (className.equals("/") && methodName.equals("/"))){
            return "/";
        }
        String p = className;
        if(methodName.isEmpty() || className.isEmpty()){
            p = className+methodName;
        }else if(methodName.charAt(0)!='/' && className.charAt(className.length()-1)!='/'){
            p = className+'/'+methodName;
        }else{
            p = className+methodName;
        }
        return p.charAt(0)=='/' ? p:'/'+p;
    }
    /**
     * 路径做为参数的方法
     * @param method
     * @return
     */
    protected static boolean isPathArg(Executable method) {
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for(int i=1;i<parameterAnnotations.length;i++){
            Annotation[] as = parameterAnnotations[i];
            if(as[0] instanceof PathParam){
                return true;
            }
        }
        return false;
    }
    /**
     * 检查类是否适合ci路由
     * @param clazz
     * @return
     */
    public static boolean check(Class<?> clazz) {
        boolean canInstance = false;
        for(Constructor<?> constructor : clazz.getConstructors()){
            if(constructor.getParameterCount()==0){
                canInstance = true;
            }
        }
        return canInstance;
    }
    /**
     * 检查方法是否适合ci路由
     * @param method
     * @return
     */
    public static boolean check(Executable method) {
        if(method.getParameterCount()>0){
            Class<?>[] types = method.getParameterTypes();
            //按CI规定，第一个参数必须为请求上下文
            if(types[0]==CiContext.class){
                for(int i=1; i<types.length; i++){
                    if(!isWebType(types[i])){
                        return false;
                    }
                }
                return true;
            }
        }
        return false;
    }

    /**
     * 检测支持的参数
     * @param clazz
     * @return
     */
    protected static boolean isWebType(Class<?> clazz) {
        return clazz.isPrimitive() || 
        clazz==Boolean.class ||
        clazz==Byte.class ||
        clazz==Short.class ||
        clazz==Integer.class ||
        clazz==Long.class ||
        clazz==Float.class ||
        clazz==Double.class ||
        clazz==Character.class ||
        clazz==String.class ||
        clazz==byte[].class ||
        clazz==File.class ||
        clazz==String[].class||
        java.util.Date.class.isAssignableFrom(clazz) ||
        Map.class.isAssignableFrom(clazz) ||
        Collection.class.isAssignableFrom(clazz)
        ;
    }


}
